import pandas as pd
import numpy as np
import warnings
import os

# 忽略关于默认样式的警告
warnings.filterwarnings('ignore', category=UserWarning, message='Workbook contains no default style, apply openpyxl\'s default')
warnings.filterwarnings('ignore', category=UserWarning, message='Port 8787 is already in use.Perhaps you already have a cluster running?')


print("1. 使用pandas读取大型Excel文件")
file_path = r'D:\\文件\\大四\\毕业论文\\数据\\20250408'
file_names = ['TRD_Dalyr', 'TRD_Dalyr1', 'TRD_Dalyr2', 'TRD_Dalyr3', 'TRD_Dalyr4', 'TRD_Dalyr5']

data_1_path = os.path.join(file_path, f'{file_names[0]}.xlsx')
data_2_path = os.path.join(file_path, f'{file_names[1]}.xlsx')
data_3_path = os.path.join(file_path, f'{file_names[2]}.xlsx')
data_4_path = os.path.join(file_path, f'{file_names[3]}.xlsx')
data_5_path = os.path.join(file_path, f'{file_names[4]}.xlsx')
data_6_path = os.path.join(file_path, f'{file_names[5]}.xlsx')

data_1 = pd.read_excel(data_1_path, skiprows=[1,2])
print("data 1 finish")
data_2 = pd.read_excel(data_2_path, skiprows=[1,2])
print("data 2 finish")
data_3 = pd.read_excel(data_3_path, skiprows=[1,2])
print("data 3 finish")
data_4 = pd.read_excel(data_4_path, skiprows=[1,2])
print("data 4 finish")
data_5 = pd.read_excel(data_5_path, skiprows=[1,2])
print("data 5 finish")
data_6 = pd.read_excel(data_6_path, skiprows=[1,2])
print("data 6 finish")

data = pd.concat([data_1,data_2,data_3,data_4,data_5,data_6])
print(type(data))
print(data)
data['LogDretwd'] = np.log(data['Dretwd'] + 1)  # 对'Dretwd'列进行对数转换（加1避免对0或负值取对数）!!!
print(data)

stock_codes = data['Stkcd'].unique()  # 使用.unique()方法提取'Stkcd'列中的唯一值，确保每个股票只出现一次
print(stock_codes)


# 创建空DataFrame，索引为唯一的交易日期（去重后的日期列表）
# 使用.unique()确保每个交易日只出现一次，作为行索引
# 该DataFrame将用于存储各股票的对数收益率时间序列
df = pd.DataFrame(index=data['Trddt'].unique())
for stkcd in stock_codes:
    temp = data[data['Stkcd'] == stkcd]  # 筛选出当前股票的所有交易记录（布尔索引过滤）
    if temp.shape[0] == df.shape[0]:
        # 将当前股票的对数收益率列添加到df中
        # 使用.values将pandas Series转换为numpy数组，提升赋值效率
        df[stkcd] = temp['LogDretwd'].values
print(df)

# 计算各股票的平均收益率（按列计算均值）
# 结果为Series，索引为股票代码，值为对应平均收益率
mean_returns = df.mean()
print(mean_returns)
# 计算各股票收益率的标准差（按列计算标准差）
# 标准差衡量收益率的波动性（风险）
std_returns = df.std()
print(std_returns)
# 计算收益率的协方差矩阵
# 协方差矩阵反映股票间的收益联动性：
# - 对角线元素为各股票收益率的方差
# - 非对角线元素为两两股票的协方差
# 该矩阵是投资组合优化的核心输入参数
cov_matrix = df.cov()
print(cov_matrix)


# 定义风险计算函数（投资组合标准差）
# 参数：
#   x - 权重向量（各股票配置比例）
#   cov_matrix - 收益率协方差矩阵
# 公式：σ_p = √(xᵀΣx)，其中Σ为协方差矩阵
def getRisk(x, cov_matrix):
    return np.sqrt(np.dot(x, np.dot(cov_matrix, x)))


# 定义收益计算函数（投资组合预期收益）
# 参数：
#   x - 权重向量
#   mean_returns - 各股票平均收益率
# 公式：E(R_p) = xᵀμ，其中μ为平均收益率向量
def getReturn(x, mean_returns):
    return np.dot(x, mean_returns)


# 获取股票数量（即DataFrame的列数）
stock_num = df.shape[1]
print(f"stock_num{stock_num}")

# 创建等权重投资组合（均匀分配资金）
# 初始权重向量：每个股票分配相同比例（1/N）
# 这是最简单的配置策略，后续可作为优化起点
initiate_x = [1 / stock_num] * stock_num

from scipy import optimize

# 初始化有效前沿存储列表
# 每个元素格式为：(目标收益率, 对应最小风险, 权重向量)
frontier = list()

# 生成20个等间距的目标收益率（从最低到最高平均收益率）
# 使用np.linspace确保均匀覆盖整个收益范围
for target_return in np.linspace(mean_returns.min(), mean_returns.max(), 20):
    print(f"正在计算目标收益率: {target_return:.4f}")

    # 设置权重约束条件（不允许卖空，即权重在0-1之间）
    bounds = tuple((0, 1) for _ in range(stock_num))

    # 定义优化约束条件：
    # 1. 收益率等式约束：投资组合收益必须等于目标值
    # 2. 权重总和等式约束：权重之和必须等于1
    constraints = (
        {'type': 'eq', 'fun': lambda x: target_return - getReturn(x, mean_returns)},  # 收益约束
        {'type': 'eq', 'fun': lambda x: 1 - sum(x)}  # 权重总和约束
    )

    # 执行风险最小化优化（使用序列最小二乘规划算法SLSQP）
    # 参数说明：
    # - objective: 目标函数（最小化风险）
    # - x0: 初始权重向量（等权重）
    # - args: 传递给目标函数的额外参数（协方差矩阵）
    # - method: 优化算法选择
    # - bounds: 变量边界约束
    # - constraints: 等式约束条件
    minimize_risk = optimize.minimize(
        getRisk,
        initiate_x,
        args=cov_matrix,
        method='SLSQP',
        bounds=bounds,
        constraints=constraints
    )

    # 更新初始权重为当前优化结果（用于下次迭代的暖启动）
    initiate_x = minimize_risk.x

    # 记录有效前沿点（目标收益、最小风险、权重向量）
    frontier.append((
        target_return,
        minimize_risk.fun,  # 优化后的最小风险值
        minimize_risk.x.tolist()  # 对应的权重向量
    ))

print("\n有效前沿计算结果（前5个点示例）:\n", frontier[:5])

# 将有效前沿数据转换为DataFrame格式
frontier_df = pd.DataFrame(frontier, columns=['Target_Return', 'Min_Risk', 'Weights'])

# 添加股票代码作为列名（方便后续分析）
frontier_df['Weights'] = frontier_df['Weights'].apply(
    lambda x: dict(zip(df.columns, x))
)

# 保存有效前沿数据到CSV文件
data_path = r"D:\\文件\\大四\\毕业论文\\写论文\\数据"
file_name_list = ['efficient_frontier', 'cov_matrix', 'mean_returns', 'std_returns']
output_path_frontier = os.path.join(data_path, f'{file_names[0]}.csv')
output_path_cov_matrix = os.path.join(data_path, f'{file_names[1]}.csv')
output_path_mean_returns = os.path.join(data_path, f'{file_names[2]}.csv')
output_path_std_returns = os.path.join(data_path, f'{file_names[3]}.csv')
frontier_df.to_csv(output_path_frontier, index=False)
print(f"\n有效前沿数据已保存至：{output_path_frontier}")

# 可选：保存协方差矩阵和收益数据（供后续验证使用）
cov_matrix.to_csv(output_path_cov_matrix)
mean_returns.to_csv(output_path_mean_returns)  # 平均收益
std_returns.to_csv(output_path_std_returns)  # 收益标准差
print("协方差矩阵和收益数据也已保存")

# 可视化有效前沿
import matplotlib.pyplot as plt


fig, ax = plt.subplots()
ax.plot(std_returns, mean_returns, '.')
ax.plot([x[1] for x in frontier], [x[0] for x in frontier])
plt.xlabel('risk')
plt.ylabel('return')
plt.show()

# 显示图形
plt.show()
